package cn.itcast.nems.journal.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.itcast.ic.common.bean.Page;
import cn.itcast.ic.common.exception.v2.BizException;
import cn.itcast.ic.common.exception.v2.BizExceptionProducer;
import cn.itcast.nems.approval.bpm.eventstrategy.BpmEventStrategy;
import cn.itcast.nems.approval.dao.entity.ApprovalConfig;
import cn.itcast.nems.approval.enumeration.ApprovalErrorBodyEnum;
import cn.itcast.nems.approval.service.ApprovalConfigService;
import cn.itcast.nems.approval.service.ApprovalFormFlowService;
import cn.itcast.nems.approval.service.ApprovalFormService;
import cn.itcast.nems.base.api.ClazzApi;
import cn.itcast.nems.base.api.SchoolApi;
import cn.itcast.nems.base.dto.ClazzDTO;
import cn.itcast.nems.base.dto.SchoolDTO;
import cn.itcast.nems.common.PageQueryModel;
import cn.itcast.nems.common.account.AccountHolder;
import cn.itcast.nems.common.entity.UUIDEntity;
import cn.itcast.nems.common.enumeration.BizNumberTypeEnum;
import cn.itcast.nems.common.enumeration.DiscountVisibilityEnum;
import cn.itcast.nems.common.enumeration.NemsErrorBodyEnum;
import cn.itcast.nems.common.service.BizNumberService;
import cn.itcast.nems.discounts.dao.dataobject.EmsDiscountsDO;
import cn.itcast.nems.employee.service.EmployeeService;
import cn.itcast.nems.journal.enumeration.JournalDiscountErrorBodyEnum;
import cn.itcast.nems.journal.enumeration.JournalDiscountLineState;
import cn.itcast.nems.journal.enumeration.JournalDiscountState;
import cn.itcast.nems.journal.enumeration.JournalStateEnum;
import cn.itcast.nems.journal.service.JournalDiscountLineService;
import cn.itcast.nems.journal.service.JournalDiscountService;
import cn.itcast.nems.journal.util.JournalUtil;
import cn.itcast.nems.manager.activity.BpmWorkflowManager;
import cn.itcast.nems.manager.util.CustomBeanUtil;
import cn.itcast.nems.oldems.service.DiscountsService;
import cn.itcast.nems.order.dao.dataobject.JournalDiscountDO;
import cn.itcast.nems.order.dao.entity.JournalDiscount;
import cn.itcast.nems.order.dao.entity.JournalDiscountLine;
import cn.itcast.nems.order.dao.mapper.JournalDiscountMapper;
import cn.itcast.nems.order.dto.JournalDiscountApiReqDTO;
import cn.itcast.nems.order.dto.JournalDiscountApiResDTO;
import cn.itcast.nems.order.dto.JournalDiscountDTO;
import cn.itcast.nems.order.dto.JournalDiscountQueryDTO;
import cn.itcast.nems.order.enumeration.DiscountTypeEnum;
import cn.itcast.nems.order.util.JournalDiscountServiceUtil;
import cn.itcast.nems.studentrecord.dao.condition.ApprovalType;
import cn.itcast.nems.studentrecord.enumeration.StudentRecordErrorBodyEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 优惠单头相关逻辑
 *
 * @author ChangLu.Gao
 * @version v1.0
 * @program nems-server
 * @description
 * @date 2023/12/08 10:41
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class JournalDiscountServiceImpl extends ServiceImpl<JournalDiscountMapper, JournalDiscount>
        implements JournalDiscountService, BpmEventStrategy {

    private final ApprovalFormService approvalFormService;

    private final BizNumberService bizNumberService;

    private final ApprovalFormFlowService approvalFormFlowService;

    private final ApprovalConfigService approvalConfigService;

    private final EmployeeService employeeService;

    private final JournalDiscountLineService journalDiscountLineService;

    private final DiscountsService discountsService;

    private final ClazzApi clazzApi;

    private final BpmWorkflowManager bpmWorkflowManager;
    @Autowired
    private SchoolApi schoolApi;

    @Override
    public String getStrategyName() {
        return ApprovalFormService.JournalType.DISCOUNT.name();
    }

    @Override
    public Page<JournalDiscountDTO> pageList(PageQueryModel<JournalDiscountQueryDTO> queryModel) {
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<JournalDiscountDO> page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>();
        page.setCurrent(queryModel.getPageNumber());
        page.setSize(queryModel.getPageSize());
        JournalDiscountDO condition = CustomBeanUtil.copyProperties(queryModel.getCondition(), new JournalDiscountDO());
        this.explicitApprovalRange(condition, queryModel.getCondition());
        JournalUtil.invokeByApprovalType(this.getJournalIdFormIds(), condition, ApprovalFormService.JournalType.DISCOUNT.name(),
                ApprovalType.valueOf(queryModel.getCondition().getRangeType()));
        IPage<JournalDiscountDO> pageData = super.getBaseMapper().findPageList(page, condition);
        if (null == pageData || CollectionUtil.isEmpty(pageData.getRecords())) {
            return Page.of(queryModel.getPageNumber(), queryModel.getPageSize(), 0, null);
        }
        //草稿状态下的优惠券ID集合
        Set<Integer> draftDiscountIds = pageData.getRecords()
                .stream()
                .filter(o -> JournalDiscountState.canEdit(o.getState()))
                .map(o -> Integer.valueOf(o.getDiscountId()))
                .collect(Collectors.toSet());
        List<EmsDiscountsDO> draftDiscounts = discountsService.queryByIds(draftDiscountIds);
        Map<Integer, EmsDiscountsDO> draftDiscountMap = draftDiscounts.stream().collect(Collectors.toMap(EmsDiscountsDO::getId, Function.identity()));
        return Page.of(queryModel.getPageNumber(),
                queryModel.getPageSize(),
                (int) pageData.getTotal(),
                pageData.getRecords().stream().map(o -> JournalDiscountServiceUtil.convert(o, draftDiscountMap)).toList()
        );
    }

    private List<String> getJournalIdFormIds() {
        ApprovalConfig config = approvalConfigService.getByJournalCode(ApprovalFormService.JournalType.DISCOUNT.name());
        Assert.notNull(config, BizExceptionProducer.produce(ApprovalErrorBodyEnum.MUST_CONFIG_APPROVAL_CONFIG, ApprovalFormService.JournalType.DISCOUNT.getCnName()));
        return bpmWorkflowManager.findWaitApproval(config.getBpmProcessKey(), AccountHolder.getRequiredAccount());
    }

    /**
     * 明确审批范围
     *
     * @param condition 查询条件
     * @return cn.itcast.nems.order.dao.dataobject.JournalDiscountDO 数据库查询条件
     * @author 高昌露
     * @since 2023/12/8 15:24
     */
    private void explicitApprovalRange(JournalDiscountDO discountDO, @NonNull JournalDiscountQueryDTO condition) {
        if (null != condition.getId()) {
            discountDO.setIds(List.of(condition.getId()));
        }
    }

    @Override
    public JournalDiscountDTO queryById(@NonNull String id) {
        var record = super.getBaseMapper().selectById(id);
        Assert.notNull(record, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "优惠凭单在"));
        var approvalFormList = this.approvalFormService.getActiveByJournalId(ApprovalFormService.JournalType.DISCOUNT, record.getId(), null);
        var lineList = journalDiscountLineService.queryByJournalDiscountId(id);
        JournalDiscountDTO result = JournalDiscountServiceUtil.convert(record, lineList);
        if (CollectionUtil.isNotEmpty(approvalFormList)) {
            result.setFormId(approvalFormList.get(0).getId());
        }
        var creator = employeeService.findOneByEmail(record.getCreatedBy());
        if (null != creator) {
            result.setCreatedByName(creator.getName());
        }
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String saveOrEdit(@NonNull JournalDiscountDTO reqParam) {
        JournalDiscount record;
        if (StringUtils.isNotBlank(reqParam.getId())) {
            record = super.getBaseMapper().selectById(reqParam.getId());
            Assert.isTrue(JournalDiscountState.canEdit(record.getState()), BizExceptionProducer.produce(StudentRecordErrorBodyEnum.APPROVE_CAN_NOT_EDIT, "凭单当前状态不可编辑"));
        } else {
            record = new JournalDiscount();
        }
        BeanUtils.copyProperties(reqParam, record, "id");
        record.setState(JournalDiscountState.DRAFT.name());
        record.setBizDate(LocalDateTime.now());
        EmsDiscountsDO discountsDO = this.reAppendLatestDiscount(record, reqParam.getDiscountId());
        Assert.isTrue(
                DiscountVisibilityEnum.PERSONAL.getVisibilityList().contains(discountsDO.getVisibility()),
                BizExceptionProducer.produce(JournalDiscountErrorBodyEnum.VISIBILITY_TYPE_ERROR)
        );
        if (DiscountVisibilityEnum.RELATIVES.getVisibilityList().contains(discountsDO.getVisibility())) {
            // 是员工亲属优惠，员工姓名、员工所在校区、员工入职日期、亲属关系 不能为空
            Assert.notNull(reqParam.getEmployeeName(), BizExceptionProducer.badRequest("员工姓名不可为空"));
            Assert.notNull(reqParam.getEmployeeCampusId(), BizExceptionProducer.badRequest("员工所在校区不可为空"));
            Assert.notNull(reqParam.getEmployeeEntryDate(), BizExceptionProducer.badRequest("员工入职日期不可为空"));
            Assert.notNull(reqParam.getEmployeeKinship(), BizExceptionProducer.badRequest("亲属关系不可为空"));
            String schoolId = reqParam.getEmployeeCampusId();
            SchoolDTO schoolDTO = schoolApi.findById(schoolId);
            if (schoolDTO != null) {
                record.setEmployeeCampusName(schoolDTO.getName());
            }
        }
        if (CollectionUtil.isEmpty(reqParam.getLineList())) {
            record.setQuantity(0);
        } else {
            record.setQuantity(reqParam.getLineList().size());
        }
        if (StringUtils.isBlank(record.getId())) {
            record.setId(bizNumberService.next(BizNumberTypeEnum.DISCOUNT));
            super.baseMapper.insert(record);
        } else {
            super.baseMapper.updateById(record);
        }
        Map<String, List<JournalDiscountLine>> lineMap = JournalDiscountServiceUtil.buildLineList(
                record.getId(),
                reqParam.getLineList(),
                this.journalDiscountLineService.queryByJournalDiscountId(record.getId())
        );
        if (CollectionUtil.isNotEmpty(lineMap.get(JournalDiscountServiceUtil.SAVE_FLAG))) {
            this.journalDiscountLineService.saveBatch(lineMap.get(JournalDiscountServiceUtil.SAVE_FLAG));
        }
        if (CollectionUtil.isNotEmpty(lineMap.get(JournalDiscountServiceUtil.DELETE_FLAG))) {
            this.journalDiscountLineService.removeBatchByIds(
                    lineMap.get(JournalDiscountServiceUtil.DELETE_FLAG)
                            .stream()
                            .map(UUIDEntity::getId)
                            .collect(Collectors.toList())
            );
        }
        if (CollectionUtil.isNotEmpty(lineMap.get(JournalDiscountServiceUtil.MODIFY_FLAG))) {
            this.journalDiscountLineService.updateBatchById(lineMap.get(JournalDiscountServiceUtil.MODIFY_FLAG));
        }
        return record.getId();
    }

    /**
     * 重新追加最新的优惠信息
     *
     * @param record     优惠凭单
     * @param discountId 优惠券ID
     * @author 高昌露
     * @since 2023/12/9 17:01
     */
    private EmsDiscountsDO reAppendLatestDiscount(@NonNull JournalDiscount record, @NonNull String discountId) {
        var discountsDO = this.discountsService.queryById(Integer.valueOf(discountId));
        if (!JournalDiscountState.canEdit(record.getState())) {
            return discountsDO;
        }
        record.setDiscountId(discountsDO.getId().toString());
        record.setDiscountName(discountsDO.getDiscountsName());
        DiscountTypeEnum discountType = DiscountTypeEnum.getByValue(discountsDO.getDiscountsStatus());
        record.setDiscountType(discountType.name());
        record.setDiscountValue(JournalDiscountServiceUtil.getDiscountValue(discountType, discountsDO));
        record.setDiscountRule(JournalDiscountServiceUtil.parseDiscountRule(discountsDO));
        return discountsDO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(@NonNull String id) {
        var record = super.getBaseMapper().selectById(id);
        Assert.notNull(record, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "优惠凭单ID：" + id));
        Assert.isTrue(AccountHolder.getRequiredAccount().equalsIgnoreCase(record.getCreatedBy()), BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_DELETE_UNAUTHORIZED));
        Assert.isTrue(JournalDiscountState.canEdit(record.getState()), BizExceptionProducer.produce(StudentRecordErrorBodyEnum.APPROVE_CAN_NOT_EDIT, "凭单当前状态不可删除"));
        this.removeById(id);
        LambdaQueryWrapper<JournalDiscountLine> condition = new LambdaQueryWrapper<>();
        condition.eq(JournalDiscountLine::getJournalDiscountId, id);
        this.journalDiscountLineService.remove(condition);
        log.info("[优惠单]员工:{}删除了凭单:{}", AccountHolder.getRequiredAccountInfo().getName(), id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String commitToBPM(@NonNull String journalId) {
        var record = super.getBaseMapper().selectById(journalId);
        Assert.notNull(record, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "优惠凭单在"));
        Assert.isTrue(JournalDiscountState.canEdit(record.getState()), BizExceptionProducer.produce(StudentRecordErrorBodyEnum.BAD_REQUEST, "凭单当前状态不可提交"));
        var lineList = this.journalDiscountLineService.queryByJournalDiscountId(journalId);
        Assert.isTrue(CollectionUtil.isNotEmpty(lineList), BizExceptionProducer.produce(StudentRecordErrorBodyEnum.BAD_REQUEST, "请选择人员"));
        var config = approvalConfigService.getByJournalCode(ApprovalFormService.JournalType.DISCOUNT.name());
        Assert.notNull(config, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "优惠凭单流程配置信息在"));
        record.setState(JournalDiscountState.PENDING.name());
        // 重新追加的优惠券信息
        EmsDiscountsDO discountsDO = this.reAppendLatestDiscount(record, record.getDiscountId());
        Assert.isTrue(
                DiscountVisibilityEnum.PERSONAL.getVisibilityList().contains(discountsDO.getVisibility()),
                BizExceptionProducer.produce(JournalDiscountErrorBodyEnum.VISIBILITY_TYPE_ERROR)
        );
        super.getBaseMapper().updateById(record);
        String formId = this.approvalFormService.start(
                ApprovalFormService.JournalType.DISCOUNT,
                journalId,
                this.buildBPMVariables(record, discountsDO),
                config.getBpmProcessKey()
        );
        log.info("[优惠单]员工({})提交了优惠单({})-关联审批单({})", AccountHolder.getRequiredAccountInfo().getName(), journalId, formId);
        return formId;
    }

    ;

    /**
     * 构建工作流需要的变量
     *
     * @param record      优惠单记录
     * @param discountsDO 优惠信息
     * @return java.util.Map<java.lang.String, java.lang.Object>
     * @author 高昌露
     * @since 2023/12/9 17:17
     */
    private Map<String, Object> buildBPMVariables(JournalDiscount record, EmsDiscountsDO discountsDO) {
        Map<String, Object> variables = new HashMap<>();
        variables.put("business_line", record.getBusinessLineId());
        variables.put("school_code", record.getEmployeeCampusId());
        variables.put("school_name", record.getEmployeeCampusName());
        variables.put("is_college_coupon", discountsDO.getVisibility().equals(3));
        return variables;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void inform(String formID, String targetAssignee) {
        approvalFormFlowService.inform(formID, targetAssignee);
        log.info("[优惠单{}]触发了收到知会", formID);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Runnable finish(String formID) {
        var approvalForm = this.approvalFormService.getById(formID);
        Assert.notNull(approvalForm, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "审批单在"));
        var record = super.getBaseMapper().selectById(approvalForm.getJournalId());
        Assert.notNull(record, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "优惠单在"));
        if (JournalDiscountState.FINISHED.name().equals(record.getState())) {
            return null;
        }
        record.setState(JournalDiscountState.FINISHED.name());
        super.baseMapper.updateById(record);
        this.journalDiscountLineService.batchValidByJournalDiscountId(record.getId());
        log.info("[优惠单{}]审批完成,流程结束", formID);
        return null;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void approve(String formID, ApprovalFormService.ApproveResult result, String comment, final String form, Integer applyType) {
        var approvalForm = this.approvalFormService.getById(formID);
        Assert.notNull(approvalForm, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "审批单在"));
        var record = super.getBaseMapper().selectById(approvalForm.getJournalId());
        Assert.notNull(record, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "优惠单在"));
        Assert.isTrue(JournalDiscountState.canApprove(record.getState()),
                BizExceptionProducer.produce(StudentRecordErrorBodyEnum.WORKFLOW_NO_PENDING_APPROVING)
        );
//        if (ApprovalFormService.ApproveResult.REJECT.equals(result)) {
//            record.setState(JournalDiscountState.REJECTED.name());
//        } else {
//            record.setState(JournalDiscountState.APPROVING.name());
//        }
//        super.baseMapper.updateById(record);
        String journalId = approvalForm.getJournalId();
        if (ApprovalFormService.ApproveResult.REJECT == result) {
            // 审批未通过
            lambdaUpdate().eq(JournalDiscount::getId, journalId).set(JournalDiscount::getState, JournalStateEnum.REJECTED.name()).update();
        } else {
            JournalStateEnum journalStateEnum = JournalStateEnum.APPROVING;
            //  1：审批（默认））2：驳回到发起人）3：驳回上一级）
            if (applyType != null && applyType == 2) {
                journalStateEnum = JournalStateEnum.DRAFT;
            } else if (applyType != null && applyType == 3) {
                journalStateEnum = JournalStateEnum.PENDING;
            }
            // 审批通过-修改为审核中
            lambdaUpdate().eq(JournalDiscount::getId, journalId).set(JournalDiscount::getState, journalStateEnum.name()).update();
        }

        this.approvalFormService.approve(formID, result, comment, form, applyType);
        log.info("[优惠单{}]审批人:{},审批结果:{}", formID, AccountHolder.getRequiredAccountInfo().getName(), result.name());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancel(String formID) {
        var approvalForm = this.approvalFormService.getById(formID);
        Assert.notNull(approvalForm, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "审批单在"));
        var record = super.getBaseMapper().selectById(approvalForm.getJournalId());
        Assert.notNull(record, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "优惠单在"));
        Assert.isTrue(JournalDiscountState.canCancel(record.getState()),
                BizExceptionProducer.produce(StudentRecordErrorBodyEnum.WORKFLOW_CANCEL_NO_PENDING, "凭单当前状态不可撤销")
        );
        Assert.isTrue(record.getCreatedBy().equals(AccountHolder.getRequiredAccountInfo().getEmail()),
                BizExceptionProducer.produce(StudentRecordErrorBodyEnum.WORKFLOW_CANCEL_NO_CREATED_BY, "仅允许提交人进行撤单")
        );
        record.setState(JournalDiscountState.DRAFT.name());
        super.baseMapper.updateById(record);
        this.approvalFormService.cancel(formID);
        log.info("[优惠单{}]员工:{}操作了单据撤销", formID, AccountHolder.getRequiredAccountInfo().getName());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rollbackRequester(String formID) {
        var approvalForm = this.approvalFormService.getById(formID);
        Assert.notNull(approvalForm, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "审批单在"));
        var record = super.getBaseMapper().selectById(approvalForm.getJournalId());
        Assert.notNull(record, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "优惠单在"));
        if (record.getState().equals(JournalDiscountState.DRAFT.name())) {
            return;
        }
        record.setState(JournalDiscountState.DRAFT.name());
        super.baseMapper.updateById(record);
//        this.approvalFormService.cancel(formID);
        log.info("[优惠单{}]员工:{}操作了单据驳回", formID, AccountHolder.getRequiredAccountInfo().getName());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void remind(String formID, String targetAssignee) {
        this.approvalFormService.remind(formID, targetAssignee);
        log.info("[优惠单{}]员工:{}操作了催办, 催办对象:{}", formID, AccountHolder.getRequiredAccountInfo().getName(), targetAssignee);
    }

    /**
     * 使用优惠单前的校验
     *
     * @param params 请求参数
     * @author 高昌露
     * @since 2023/12/10 14:47
     */
    private void checkForUse(@NonNull JournalDiscountApiReqDTO params, @NonNull ClazzDTO clazzDTO) throws BizException {
        Assert.notNull(params, BizExceptionProducer.produce(NemsErrorBodyEnum.PARAM_EMPTY, "请求参数"));
        Assert.isTrue(
                StringUtils.isNotEmpty(params.getJournalDiscountLineId()),
                BizExceptionProducer.produce(NemsErrorBodyEnum.PARAM_EMPTY, "优惠单行ID")
        );
        Assert.isFalse(
                StringUtils.isBlank(params.getCustomerId()) &&
                        (null == params.getStudentId() || params.getStudentId() < 0),
                BizExceptionProducer.produce(NemsErrorBodyEnum.PARAM_EMPTY, "客户ID或学员ID")
        );
        Assert.isTrue(
                null != params.getClazzId() && params.getClazzId() > 0,
                BizExceptionProducer.produce(NemsErrorBodyEnum.PARAM_EMPTY, "班级ID")
        );
        Assert.notNull(clazzDTO, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "班级在"));
        var lineRecord = this.journalDiscountLineService.getById(params.getJournalDiscountLineId());
        Assert.notNull(lineRecord, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "优惠单行在"));
        Assert.isTrue(
                JournalDiscountLineState.canWriteOff(lineRecord.getState()),
                BizExceptionProducer.produce(JournalDiscountErrorBodyEnum.WRITE_OFF_ERROR)
        );
        Assert.isTrue(
                StringUtils.equals(lineRecord.getCustomerId(), params.getCustomerId()) ||
                        Objects.equals(lineRecord.getStudentId(), params.getStudentId()),
                BizExceptionProducer.produce(JournalDiscountErrorBodyEnum.WRITE_OFF_ERROR_FOR_PERSON, params.getAffectedCustomerId())
        );
        var headRecord = this.queryById(lineRecord.getJournalDiscountId());
        this.journalDiscountCheck(params, headRecord, clazzDTO);
    }

    /**
     * 优惠单层面可用性校验
     *
     * @param params     请求参数
     * @param headRecord 优惠单信息
     * @param clazzDTO   目标班级信息
     * @author 高昌露
     * @since 2023/12/10 15:23
     */
    private void journalDiscountCheck(@NonNull JournalDiscountApiReqDTO params,
                                      @NonNull JournalDiscountDTO headRecord,
                                      @NonNull ClazzDTO clazzDTO) {
        Assert.notNull(headRecord, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "优惠单在"));
        params.getScenario().getBizDateCheckScope().forEach(bizDateCheckScope -> {
            switch (bizDateCheckScope) {
                case JournalDiscountApiReqDTO.Scenario.CLAZZ_START_DATE -> Assert.isTrue(
                        JournalDiscountServiceUtil.atBetween(clazzDTO.getStartDate().atStartOfDay(), headRecord.getClazzBeginDate(), headRecord.getClazzEndDate()),
                        BizExceptionProducer.produce(JournalDiscountErrorBodyEnum.BETWEEN_TIME_ERROR, "开班时间")
                );
                case JournalDiscountApiReqDTO.Scenario.FEE_DATE -> Assert.isTrue(
                        JournalDiscountServiceUtil.atBetween(params.getBizDateTime(), headRecord.getFeeBeginDate(), headRecord.getFeeEndDate()),
                        BizExceptionProducer.produce(JournalDiscountErrorBodyEnum.BETWEEN_TIME_ERROR, "缴费时间")
                );
                default -> throw new IllegalArgumentException("Unsupported bizDateCheckScope: " + bizDateCheckScope);
            }
        });
        if (null != headRecord.getApplyClazzType()) {
            Assert.isTrue(
                    JournalDiscountServiceUtil.checkClazzScope(clazzDTO, headRecord.getApplyClazzType()),
                    BizExceptionProducer.produce(JournalDiscountErrorBodyEnum.CLAZZ_TYPE_ERROR)
            );
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void writeOff(@NonNull JournalDiscountApiReqDTO params) throws BizException {
        // 理论上应该加上全局锁, 但实际场景碰到的可能性很低, 后续有需要再加吧
        var clazzDTO = clazzApi.queryByClassId(params.getClazzId());
        this.checkForUse(params, clazzDTO);
        this.journalDiscountLineService.writeOff(params.getJournalDiscountLineId());
        log.info("[优惠单]核销, 请求参数:{}", params);
    }

    @Override
    public boolean validityVerify(JournalDiscountApiReqDTO params) {
        try {
            var clazzDTO = clazzApi.queryByClassId(params.getClazzId());
            this.checkForUse(params, clazzDTO);
        } catch (Exception e) {
            log.warn("[优惠单]有效性校验, 请求参数:{}, 失败原因: {}", params, e.getMessage());
            return false;
        }
        return true;
    }

    @Override
    public JournalDiscountApiResDTO queryByLineId(JournalDiscountApiReqDTO params) {
        Assert.isTrue(
                StringUtils.isNotEmpty(params.getJournalDiscountLineId()),
                BizExceptionProducer.produce(NemsErrorBodyEnum.PARAM_EMPTY, "优惠单行ID")
        );
        var lineRecord = this.journalDiscountLineService.getById(params.getJournalDiscountLineId());
        Assert.notNull(lineRecord, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "优惠单行在"));
        var headRecord = this.getById(lineRecord.getJournalDiscountId());
        Assert.notNull(headRecord, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "优惠单在"));
        var journalDiscountDTO = JournalDiscountServiceUtil.convert(headRecord, List.of(lineRecord));
        return JournalDiscountServiceUtil.convert(journalDiscountDTO).get(0);
    }

    @Override
    public List<JournalDiscountApiResDTO> usableList(JournalDiscountApiReqDTO params) {
        var clazzDTO = clazzApi.queryByClassId(params.getClazzId());
        Assert.notNull(clazzDTO, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "班级在"));
        var lineList = this.journalDiscountLineService.queryByStudentIdOrCustomerId(params.getStudentId(), params.getCustomerId());
        Map<String, List<JournalDiscountLine>> lineMap = lineList.stream().collect(Collectors.groupingBy(JournalDiscountLine::getJournalDiscountId));
        List<String> journalDiscountIds = lineList.stream().map(JournalDiscountLine::getJournalDiscountId).toList();
        if (CollectionUtil.isEmpty(journalDiscountIds)) {
            return null;
        }
        var headList = super.listByIds(journalDiscountIds);
        return headList.stream()
                .map(o -> JournalDiscountServiceUtil.convert(o, lineMap.get(o.getId())))
                .filter(o -> {
                    try {
                        this.journalDiscountCheck(params, o, clazzDTO);
                    } catch (Exception e) {
                        log.warn("[获取可使用优惠单]请求参数:{}, 优惠单ID:{}, 不可使用原因:{}", params, o.getId(), e.getMessage());
                        return false;
                    }
                    return true;
                })
                .flatMap(o -> JournalDiscountServiceUtil.convert(o).stream())
                .toList();
    }
}
